﻿namespace WebPacker.Tool.Sources
{
	using System;
	using System.IO;
	using System.Linq;
	using System.Xml.XPath;
	using NXml.Serialization;
	using NXml.Serialization.Mappings;
	using NXml.Serialization.Mappings.Providers;
	using WebPacker.Configuration;
	using WebPacker.Filters;
	using WebPacker.Tool.Sources;
	using WebPacker.VersionGenerators;

	internal class WebConfigSource : ISource
	{
		private readonly string fileName;
		private Configuration configuration;
		private PackInfo[] packs;

		public WebConfigSource(string fileName)
			: this(Configuration.GetDefaults(), fileName)
		{
		}

		public WebConfigSource(Configuration configuration, string fileName)
		{
			if (fileName == null)
			{
				throw new ArgumentNullException("fileName");
			}

			this.fileName = fileName;
			this.configuration = configuration;
			this.configuration.BasePath = Path.GetDirectoryName(fileName);
		}

		public PackInfo[] Packs
		{
			get { return packs; }
		}

		public Configuration Configuration
		{
			get { return configuration; }
		}

		public void Open()
		{
			if (!File.Exists(fileName))
			{
				throw ToolException.FileNotFound(fileName);
			}

			var basePath = configuration.BasePath;

			var xpath = new XPathDocument(fileName);
			var serializer = new XmlSerializer<WebPackerConfiguration>(GetSerializerContext(), "web.packer", false);
			var navigator = xpath.CreateNavigator();
			var iterator = navigator.SelectSingleNode("/configuration/web.packer");
			var reader = iterator.ReadSubtree();
			var webConfig = serializer.Deserialize(reader);

			if (configuration.Output == null)
			{
				var providerNavigator = iterator.SelectSingleNode("provider");
				reader = providerNavigator.ReadSubtree();

				if (reader.Read())
				{
					var sourcePath = reader.GetAttribute("source");

					if (sourcePath != null)
					{
						var index = sourcePath.LastIndexOfAny(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar });

						if (index >= 0)
						{
							configuration.Output = sourcePath.Substring(0, index);
						}

						configuration.MetaFileName = sourcePath;
					}
					else
					{
						configuration.Output = reader.GetAttribute("outputPath");
					}
				}
			}

			if (webConfig.VersionGenerators != null)
			{
				configuration.VersionGenerators = webConfig.VersionGenerators;
			}

			if (webConfig.Filters != null)
			{
				configuration.Filters = webConfig.Filters;
			}

			if (webConfig.Packs != null)
			{
				if (webConfig.Packs.VersionGeneratorName != null)
				{
					configuration.VersionGeneratorName = webConfig.Packs.VersionGeneratorName;
				}

				packs = webConfig.Packs.ToArray();

				if (webConfig.Packs.Source != null)
				{
					var sourceFile = webConfig.Packs.Source;

					if (sourceFile.StartsWith("~/"))
					{
						sourceFile = Path.Combine(basePath, sourceFile.Substring(2));
					}

					var packInfos = WebPackerConfiguration.DeserializePackInfoCollection(sourceFile);

					if (packInfos.VersionGeneratorName != null)
					{
						configuration.VersionGeneratorName = packInfos.VersionGeneratorName;
					}

					packs = packInfos.ToArray();
				}
			}
		}

		private XmlSerializerContext GetSerializerContext()
		{
			var context = new XmlSerializerContext
			{
				TypeResolver = new WebPackerTypeResolver(),
				MappingProvider = new XmlLambdaMappingProvider(new WebPackerMappingProvider())
				{
					Mapper = (type) =>
					{
						if (type == typeof(WebPackerConfiguration))
						{
							return new XmlObjectMapping(type)
							{
								Properties = new XmlPropertyMapping[]
								{
									new XmlPropertyMapping(typeof(IFilter[]), "Filters")
									{
										MappingNode = XmlMappingNode.Element,
										XmlName = "filters"
									},
									new XmlPropertyMapping(typeof(IVersionGenerator[]), "VersionGenerators")
									{
										MappingNode = XmlMappingNode.Element,
										XmlName = "versionGenerators"
									},
									new XmlPropertyMapping(typeof(PackInfoCollection), "Packs")
									{
										MappingNode = XmlMappingNode.Element,
										XmlName = "packs"
									}
								}
							};
						}

						return null;
					}
				}
			};

			context.Converters.Add(new EncodingConverter());
			context.Serializers.Add(new PackInfoCollectionSerializer());
			return context;
		}
	}
}